﻿#if DEBUG
#define ThrowNull
#endif
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Diagnostics;

namespace System {
	/// <summary>
	/// Base packaging object object.
	/// </summary>
#if !SILVERLIGHT
	[Serializable()]
#endif
	public class PackageBase
		#if !SILVERLIGHT
		: ICloneable 
		#endif
	{
		#region Fields
		[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
		[DebuggerDisplay("Local Values")]
		private readonly Object[] _localValues;

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private readonly Type _localType;

		[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
		private Dictionary<PackageProperty, Object> _publicValues;

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		#if !SILVERLIGHT
		[NonSerialized()]
		private Object _syncRoot = new object();
#endif
#if !SILVERLIGHT
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		
		[NonSerialized()]
#endif
		private EventHandler<PackageValueChangedEventArgs> _valueChanged;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
#if !SILVERLIGHT
		[NonSerialized()]
#endif
		private EventHandler<PackageValueChangingEventArgs> _valueChanging;
		#endregion
		#region Constructor
		/// <summary>
		/// Initializes a new instance of the <see cref="PackageBase"/> class.
		/// </summary>
		public PackageBase() {
			_localType = this.GetType();
			_localValues = this.InitLocalValues(_localType);
		}
		#endregion
		#region Private
		private Object[] InitLocalValues(Type type) {
			Object[] current = this.ReflectLocalValues(type);
			Object[] reflect = null;
			Object[] local = null;
			if (type.BaseType.IsSubclassOf(typeof(PackageBase))) {
				reflect = InitLocalValues(type.BaseType);
			}

			if (reflect != null && reflect.Length > 0) {
				local = new Object[current.Length + reflect.Length];

				Array.Copy(current, 0, local, 0, current.Length);
				Array.Copy(reflect, 0, local, current.Length, reflect.Length);
			} else {
				local = current;
			}
			return local;
		}
		private Object[] ReflectLocalValues(Type type) {
			FieldInfo[] array = type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			List<Object> list = new List<Object>();
			foreach (FieldInfo field in array) {
				if (field.FieldType == typeof(PackageProperty)) {
					if ((field.Attributes & FieldAttributes.InitOnly) == FieldAttributes.InitOnly) {
						list.Add(null);
					} else {
						throw new Exception("Found PackageProperty which is not marked as readonly.");
					}
				}
			}
			return list.ToArray();
		}
		private PackageProperty[] ReflectProperty(Type type) {
			FieldInfo[] array = type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			List<PackageProperty> list = new List<PackageProperty>();
			foreach (FieldInfo field in array) {
				if (field.FieldType == typeof(PackageProperty)) {
					if ((field.Attributes & FieldAttributes.InitOnly) == FieldAttributes.InitOnly) {
						list.Add(field.GetValue(null) as PackageProperty);
					} else {
						throw new Exception("Found PackageProperty which is not marked as readonly.");
					}
				}
			}
			return list.ToArray();
		}
		private Int32 GetIndex(PackageProperty property) {
			if (_localType == property.OwnerType) {
				return property.LocalIndex;
			}
			return GetOffset(_localType,property.OwnerType) + property.LocalIndex;
		}
		private Int32 GetOffset(Type currentType, Type targetType) {
			if (currentType == targetType) {
				return 0;
			}
			Int32 offset = PackageProperty.GetPropertyCount(currentType);
			if (currentType.BaseType.IsSubclassOf(typeof(PackageUnit))) {
				offset += GetOffset(currentType.BaseType, targetType);
			}

			return offset;
		}

		private Object GetLocalValue(PackageProperty property) {
			Int32 index = GetIndex(property);
			if (_localValues[index] == null && property.PropertyType.IsValueType) {
				return Activator.CreateInstance(property.PropertyType);
			}
			return _localValues[index];
		}
		private Object GetPublicValue(PackageProperty property) {
			if (_publicValues != null) {
				if (_publicValues.ContainsKey(property)) {
					return _publicValues[property];
				}
			}
			//Default value.
			if (property.PropertyType.IsValueType) {
				return Activator.CreateInstance(property.PropertyType);
			}
			return null;
		}

		private void SetLocalValue(PackageProperty property, Object value) {
#if ThrowNull
			if (property == null) {
				throw new ArgumentNullException("property");
			}
#endif
			Int32 index = GetIndex(property);
			if (!this.OnValueChanging(property, _localValues[index], value)) {
				_localValues[index] = value;
				this.OnValueChanged(property, value);
			}
		}
		private void SetPublicValue(PackageProperty property, Object value) {
#if ThrowNull
			if (property == null) {
				throw new ArgumentNullException("property");
			}
#endif

			if (_publicValues == null && (value != null)) {
				_publicValues = new Dictionary<PackageProperty, object>();
			}
			if (_publicValues != null) {
				if (value == null) {
					_publicValues.Remove(property);
					if (_publicValues.Count == 0) {
						_publicValues = null;
					}
				} else {
					object currentValue = (_publicValues.ContainsKey(property) ? _publicValues[property] : null);
					if (!this.OnValueChanging(property, currentValue, value)) {
						_publicValues[property] = value;
						this.OnValueChanged(property, value);
					}
				}
			}
		}
		#endregion
		#region Protected
		/// <summary>
		/// Called when Value changes.
		/// </summary>
		/// <param name="property">The property.</param>
		/// <param name="value">The value.</param>
		protected virtual void OnValueChanged(PackageProperty property, Object value) {
			if (_valueChanged != null) {
				_valueChanged(this, new PackageValueChangedEventArgs(this, property, value));
			}
		}
		/// <summary>
		/// Called when value is about to change.
		/// </summary>
		/// <param name="property">The property.</param>
		/// <param name="oldValue">The old value.</param>
		/// <param name="newValue">The new value.</param>
		protected virtual Boolean OnValueChanging(PackageProperty property, Object oldValue,Object newValue) {
			if (_valueChanging != null) {
				PackageValueChangingEventArgs args = new PackageValueChangingEventArgs(this, property, oldValue, newValue);
				_valueChanging(this, args);
				return args.Cancel;
			}
			return false;
		}
		#endregion
		#region Public
		/// <summary>
		/// Creates a new object that is a copy of the current instance.
		/// </summary>
		/// <returns>
		/// A new object that is a copy of this instance.
		/// </returns>
		public virtual Object Clone() {
			Object obj = Activator.CreateInstance(_localType);

			PackageBase.CopyBothValues(this, (PackageBase)obj);

			return obj;
		}
		/// <summary>
		/// Sets the value.
		/// </summary>
		/// <param name="property">The property.</param>
		/// <param name="value">The value.</param>
		public void SetValue(PackageProperty property, Object value) {
			if (property == null) {
				throw new ArgumentNullException("property");
			}
			if (!this.IsValidValue(property, value)) {
				throw new InvalidCastException(String.Format("Unable to cast {0} to {1}.", value.GetType().Name, property.PropertyType.Name));
			}
			if (this.IsLocalProperty(property)) {
				this.SetLocalValue(property, value);
			} else {
				this.SetPublicValue(property, value);
			}
		}
		/// <summary>
		/// Gets the value.
		/// </summary>
		/// <param name="property">The property.</param>
		/// <returns></returns>
		public Object GetValue(PackageProperty property) {
			if (this.IsLocalProperty(property)) {
				return this.GetLocalValue(property);
			} else {
				return this.GetPublicValue(property);
			}
		}
		/// <summary>
		/// Determines whether this object contains value.
		/// </summary>
		/// <param name="property">The property.</param>
		/// <returns>
		/// 	<c>true</c> if this object contains value; otherwise, <c>false</c>.
		/// </returns>
		public Boolean ContainsValue(PackageProperty property) {
			Object obj = this.GetValue(property);

			return (obj != null);
		}
		/// <summary>
		/// Removes the value.
		/// </summary>
		/// <param name="property">The property.</param>
		/// <returns></returns>
		public Boolean RemoveValue(PackageProperty property) {
			if (this.IsLocalProperty(property)) {
				_localValues[GetIndex(property)] = null;
				return true;
			} else {
				if (_publicValues != null) {
					return _publicValues.Remove(property);
				}
				return false;
			}
		}
		/// <summary>
		/// Determines whether property is local.
		/// </summary>
		/// <param name="property">The property.</param>
		/// <returns>
		/// 	<c>true</c> if property is local; otherwise, <c>false</c>.
		/// </returns>
		public Boolean IsLocalProperty(PackageProperty property) {
#if ThrowNull
			if (property == null) {
				throw new ArgumentNullException("property");
			}
#endif
			return (_localType == property.OwnerType || _localType.IsSubclassOf(property.OwnerType));
		}
		/// <summary>
		/// Determines whether property is public.
		/// </summary>
		/// <param name="property">The property.</param>
		/// <returns>
		/// 	<c>true</c> if property is public; otherwise, <c>false</c>.
		/// </returns>
		public Boolean IsPublicProperty(PackageProperty property) {
			return !IsLocalProperty(property);
		}
		/// <summary>
		/// Determines whether type is valid for property.
		/// </summary>
		/// <param name="property">The property.</param>
		/// <param name="targetType">Type of the target.</param>
		/// <returns>
		/// 	<c>true</c> if Type is valid; otherwise, <c>false</c>.
		/// </returns>
		public Boolean IsValidType(PackageProperty property, Type targetType) {
#if ThrowNull
			if (property == null) {
				throw new ArgumentNullException("property");
			}
			if (targetType == null) {
				throw new ArgumentNullException("targetType");
			}
#endif
			return (targetType == property.PropertyType || targetType.IsSubclassOf(property.PropertyType) || property.PropertyType.IsAssignableFrom(targetType));
		}
		/// <summary>
		/// Determines whether value is valid for property.
		/// </summary>
		/// <param name="property">The property.</param>
		/// <param name="value">The value.</param>
		/// <returns>
		/// 	<c>true</c> if value is valid; otherwise, <c>false</c>.
		/// </returns>
		public Boolean IsValidValue(PackageProperty property, Object value) {
#if ThrowNull
			if (property == null) {
				throw new ArgumentNullException("property");
			}
			if (value == null && property.PropertyType.IsValueType && property.PropertyType == typeof(Nullable<>)) {
				throw new ArgumentNullException("value");
			}
#endif
			if (value == null && (!property.PropertyType.IsValueType || property.PropertyType != typeof(Nullable<>))) {
				return true;
			}
			return (this.IsValidType(property, value.GetType()));
		}
		#endregion
		#region Static
		/// <summary>
		/// Copies the local values.
		/// </summary>
		/// <param name="source">The source.</param>
		/// <param name="destination">The destination.</param>
		/// <returns>True, if any value have been copied.</returns>
		public static bool CopyLocalValues(PackageBase source, PackageBase destination) {
#if ThrowNull
			if (source == null) {
				throw new ArgumentNullException("source");
			}
			if (destination == null) {
				throw new ArgumentNullException("destination");
			}
#endif

			PackageProperty[] array = PackageProperty.GetProperties(source.GetType());
			if (array != null && array.Length > 0) {
				for (int i = 0; i < array.Length; i++) {
					destination.SetValue(array[i], source.GetValue(array[i]));
				}
				return true;
			}
			return false;
		}
		/// <summary>
		/// Copies the public values.
		/// </summary>
		/// <param name="source">The source.</param>
		/// <param name="destination">The destination.</param>
		/// <returns>True, if any value have been copied.</returns>
		public static bool CopyPublicValues(PackageBase source, PackageBase destination) {
#if ThrowNull
			if (source == null) {
				throw new ArgumentNullException("source");
			}
			if (destination == null) {
				throw new ArgumentNullException("destination");
			}
#endif

			if (source._publicValues != null && source._publicValues.Count > 0) {
				foreach (KeyValuePair<PackageProperty, Object> pair in source._publicValues) {
					destination.SetValue(pair.Key, pair.Value);
				}
				return true;
			}
			
			return false;
		}
		/// <summary>
		/// Copies the both values.
		/// </summary>
		/// <param name="source">The source.</param>
		/// <param name="destination">The destination.</param>
		/// <returns>True, if any value have been copied.</returns>
		public static bool CopyBothValues(PackageBase source, PackageBase destination) {
#if ThrowNull
			if (source == null) {
				throw new ArgumentNullException("source");
			}
			if (destination == null) {
				throw new ArgumentNullException("destination");
			}
#endif
			Boolean copyLocal = CopyLocalValues(source, destination);
			Boolean copyPublic = CopyPublicValues(source, destination);
			return (copyLocal || copyPublic);
		}
		/// <summary>
		/// Copies the owner values.
		/// </summary>
		/// <param name="source">The source.</param>
		/// <param name="destination">The destination.</param>
		/// <returns></returns>
		public static bool CopyOwnerValues(PackageBase source, PackageBase destination) {
#if ThrowNull
			if (source == null) {
				throw new ArgumentNullException("source");
			}
			if (destination == null) {
				throw new ArgumentNullException("destination");
			}
#endif
			PackageProperty[] array = PackageProperty.GetProperties(source.GetType());
			if (array != null && array.Length > 0) {
				for (int i = 0; i < array.Length; i++) {
					if (array[i].OwnerType == destination._localType) {
						destination.SetValue(array[i], source.GetValue(array[i]));
					}
				}
			}
			return false;
		}
		#endregion
		#region Property

		#endregion
		#region Events
		/// <summary>
		/// Occurs when value changes.
		/// </summary>
		public event EventHandler<PackageValueChangedEventArgs> ValueChanged {
			add {
				_valueChanged = (EventHandler<PackageValueChangedEventArgs>.Combine(_valueChanged, value) as EventHandler<PackageValueChangedEventArgs>);
			}
			remove {
				_valueChanged = (EventHandler<PackageValueChangedEventArgs>.Remove(_valueChanged, value) as EventHandler<PackageValueChangedEventArgs>);
			}
		}
		/// <summary>
		/// Occurs when value is about to change.
		/// </summary>
		public event EventHandler<PackageValueChangingEventArgs> ValueChanging {
			add {
				_valueChanging = (EventHandler<PackageValueChangingEventArgs>.Combine(_valueChanging, value) as EventHandler<PackageValueChangingEventArgs>);
			}
			remove {
				_valueChanging = (EventHandler<PackageValueChangingEventArgs>.Remove(_valueChanging, value) as EventHandler<PackageValueChangingEventArgs>);
			}
		}
		#endregion
		#region Enumerator
		/// <summary>
		/// Gets the enumerator.
		/// </summary>
		/// <returns></returns>
		public IEnumerator<KeyValuePair<PackageProperty, Object>> GetEnumerator() {
			PackageProperty[] array = PackageProperty.GetProperties(_localType);
			if (array != null && array.Length > 0) {
				for (int i = 0; i < array.Length; i++) {
					yield return new KeyValuePair<PackageProperty,Object>(array[i],this.GetValue(array[i]));
				}
			}
		}
		#endregion
	}
}
